//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <ddk.h>
#include "drvpub.h"

#define VIRTADDRCACHE_MAXITEM 16
typedef struct {
    uint_t UserAddr;
    uint_t KernelAddr;
} VirtAddrCache;

class CDrvBuf : public CDeviceDriver {
public:
    CARAPI Read(
        /* [in] */ Int64 u64Offset,
        /* [in] */ Int32 bytesToRead,
        /* [out] */ MemoryBuf * pBuffer,
        /* [out] */ IEvent * * ppCompletionEvent);
    CARAPI Write(
        /* [in] */ Int64 u64Offset,
        /* [in] */ const MemoryBuf & buffer,
        /* [out] */ Int32 * pBytesWritten,
        /* [out] */ IEvent * * ppCompletionEvent);
    CARAPI Control(
        /* [in] */ Handle32 nControlCode,
        /* [in] */ const MemoryBuf & inBuffer,
        /* [out] */ MemoryBuf * pOutBuffer,
        /* [out] */ IEvent * * ppCompletionEvent);
    virtual void Dispose();
    CDrvBuf()
    {
        m_nVirtAddrNum = 0;
        memset(&m_virtAddrCache, 0, sizeof(m_virtAddrCache));
    }

private :
    void InsertVirtAddrCache(uint_t UserAddr, uint_t KernelAddr);
    void RemoveVirtAddrCache(uint_t UserAddr);
    uint_t FindKernelVirtAddr(uint_t UserAddr);

    DzMutex m_mutex;
    int m_nVirtAddrNum;
    VirtAddrCache m_virtAddrCache[VIRTADDRCACHE_MAXITEM];
};
static CDrvBuf s_drvBuf;

void CDrvBuf::InsertVirtAddrCache(uint_t UserAddr, uint_t KernelAddr)
{
    int i;

    WaitResult wr;
    m_mutex.Lock(&wr);

    Drv_Assert(m_nVirtAddrNum < VIRTADDRCACHE_MAXITEM);
    for (i = 0; i < VIRTADDRCACHE_MAXITEM; i++) {
        if (0 == m_virtAddrCache[i].UserAddr) {
            m_virtAddrCache[i].KernelAddr = KernelAddr;
            m_virtAddrCache[i].UserAddr = UserAddr;
            m_nVirtAddrNum++;
            break;
        }
    }
    m_mutex.Unlock();
    if (i == VIRTADDRCACHE_MAXITEM) Drv_Assert(0 && "CAn't Find node");
    return;
}

void CDrvBuf::RemoveVirtAddrCache(uint_t UserAddr)
{
    WaitResult wr;
    m_mutex.Lock(&wr);

    for (int i = 0; i < VIRTADDRCACHE_MAXITEM; i++) {
        if (UserAddr == m_virtAddrCache[i].UserAddr) {
            m_virtAddrCache[i].UserAddr = 0;
            m_nVirtAddrNum--;
        }
    }
    m_mutex.Unlock();
}

uint_t CDrvBuf::FindKernelVirtAddr(uint_t UserAddr)
{
    uint_t KernelAddr = 0;

    WaitResult wr;
    m_mutex.Lock(&wr);

    Drv_Assert(m_nVirtAddrNum < VIRTADDRCACHE_MAXITEM);
    for (int i = 0; i < VIRTADDRCACHE_MAXITEM; i++) {
        if (UserAddr == m_virtAddrCache[i].UserAddr) {
            KernelAddr = m_virtAddrCache[i].KernelAddr;
            Drv_Assert(KernelAddr);
        }
    }
    m_mutex.Unlock();
    return KernelAddr;
}

ECode CDrvBuf::Read(
        /* [in] */ Int64 u64Offset,
        /* [in] */ Int32 bytesToRead,
        /* [out] */ MemoryBuf * pBuffer,
        /* [out] */ IEvent * * ppCompletionEvent)
{
    return E_NOT_IMPLEMENTED;
}

ECode CDrvBuf::Write(
        /* [in] */ Int64 u64Offset,
        /* [in] */ const MemoryBuf & buffer,
        /* [out] */ Int32 * pBytesWritten,
        /* [out] */ IEvent * * ppCompletionEvent)
{
    return E_NOT_IMPLEMENTED;
}

// Used for user mode system components
ECode CDrvBuf::Control(
        /* [in] */ Handle32 nControlCode,
        /* [in] */ const MemoryBuf & inBuffer,
        /* [out] */ MemoryBuf * pOutBuffer,
        /* [out] */ IEvent * * ppCompletionEvent)
{
    switch (nControlCode) {
        case 0: {
            // Drv_AllocBuffer
            // Return a user mode share address
            uint_t uPages, uFlag;
            void * VirtAddr;
            uint_t PhysAddr, RetAddr = 0;

            if (pOutBuffer == NULL) {
                return E_INVALID_ARGUMENT;
            }
            Drv_Assert(inBuffer.GetCapacity() >= 8 && pOutBuffer->GetCapacity() >= 4);
            if (m_nVirtAddrNum < VIRTADDRCACHE_MAXITEM) {
                uPages = *(int *)inBuffer.GetPayload();
                uFlag  = *((int *)inBuffer.GetPayload() + 1);
                VirtAddr = Drv_AllocBuffer(uPages, uFlag);
                if (VirtAddr) {
                    PhysAddr = Drv_GetBufferPhysicalAddress(VirtAddr);
                    DzIoRemap( 0, uPages << PAGE_SHIFT, (Address)PhysAddr,
                              0, MemoryProtection_RW, (Address *)&RetAddr);
                    InsertVirtAddrCache(RetAddr, (uint_t)VirtAddr);
                }
            }
            pOutBuffer->Copy((Byte *)&RetAddr, sizeof(uint_t));
            break;
        }
        case 1: {
            // Drv_ReallocBuffer
            // Return a user mode share address
            void * VirtAddr, *KernelVirtAddr;
            uint_t uOldPages, uNewPages, uFlag;
            void * NewVirtAddr;
            uint_t NewPhysAddr, RetAddr = 0;

            if (pOutBuffer == NULL) {
                return E_INVALID_ARGUMENT;
            }
            Drv_Assert(inBuffer.GetCapacity() >= 16 && pOutBuffer->GetCapacity() >= 4);

            VirtAddr = (void *)inBuffer.GetPayload();
            uOldPages  = *((uint_t *)inBuffer.GetPayload() + 1);
            uNewPages  = *((uint_t *)inBuffer.GetPayload() + 2);
            uFlag  = *((uint_t *)inBuffer .GetPayload()+ 3);
            KernelVirtAddr = (void * )FindKernelVirtAddr((uint_t)VirtAddr);
            Drv_Assert(KernelVirtAddr && "Input a Invalid Virtual Address...");

            NewVirtAddr = Drv_ReallocBuffer(
                    KernelVirtAddr, uOldPages, uNewPages, uFlag);
            if (NewVirtAddr) {
                if (VirtAddr) {
                    RemoveVirtAddrCache((uint_t)VirtAddr);
                    DzIoUnmap((Address)VirtAddr, uOldPages << PAGE_SHIFT);
                }
                NewPhysAddr = Drv_GetBufferPhysicalAddress(NewVirtAddr);
                DzIoRemap( 0, uNewPages << PAGE_SHIFT, (Address)NewPhysAddr,
                          0, MemoryProtection_RW, (Address *)&RetAddr);
                InsertVirtAddrCache(RetAddr, (uint_t)NewVirtAddr);
            }
            pOutBuffer->Copy((Byte *)&RetAddr, sizeof(uint_t));
            break;
        }
        case 2: {
            // Drv_FreeBuffer
            // Use a user mode share address
            uint_t uPages;
            void * VirtAddr;
            void *  KernelVirtAddr;

            Drv_Assert(inBuffer.GetCapacity() >= 8);

            VirtAddr = (void *)inBuffer.GetPayload();
            uPages = *((int *)inBuffer.GetPayload() + 1);
            KernelVirtAddr = (void * )FindKernelVirtAddr((uint_t)VirtAddr);
            Drv_Assert(KernelVirtAddr && "Input a Invalid Virtual Address...");
            Drv_FreeBuffer(KernelVirtAddr, uPages);
            RemoveVirtAddrCache((uint_t)VirtAddr);
            DzIoUnmap((Address)VirtAddr, uPages << PAGE_SHIFT);
            break;
        }
        default:
            break;
    }
    return NOERROR;
}

void CDrvBuf::Dispose()
{
}


EXTERN IDeviceDriver * CDECL CreateDrvBuf(uint_t uDeviceNo, void *pvParameter)
{
    Drv_InitBuffer();
    InitPostern();
    return &s_drvBuf;
}
